%\newpage
%\mbox{}
\chapter{面向多核CPU的深度学习计算访存加速方法}\label{面向多核CPU的深度学习计算访存加速方法}

针对云环境中广泛使用CPU进行深度学习的实际需求，
% 本文首次通过大量的实验和分析证实了CPU在深度学习应用中效率低下的原因主要来源于多核并行时的访存带宽竞争问题，并基于该分析
本章首先通过大量实验和理论分析发现目前深度学习软件系统在多核CPU上性能较差的根本原因
是多核并行计算的访存带宽竞争问题，
进而提出一种面向多核CPU深度学习的访存加速方法ParaX。
ParaX通过“单核心单实例”（One-Instance-per-Core）而不是传统的“单CPU单实例”方法将深度学习实例分配到每个CPU核心上执行数据并行，以允许每个核心单独对其数据批次进行处理，从而避免了DNN模型每层执行时的核心同步屏障。通过将DNN中的网络层分为两类，即执行复杂算术运算的计算密集型层（如卷积和矩阵乘法），以及访存密集型层（如BN层和激活层），“单核心单实例”方法实现了访存密集型与计算密集型网络层的混合执行和不同层之间的带宽共享，大幅提高了CPU的内存带宽利用率。对于训练，ParaX采用同步SGD策略，在每轮迭代的最后更新模型参数。针对ParaX特有的CPU多核心通信场景，设计了支持NUMA（non-uniform memory access）架构的梯度服务器通信机制，利用共享内存有效减少了CPU核心之间的参数同步开销。

\section{引言}

近年来，深度学习或Deep Learning（DL）已经成为一种新兴的大数据分析方式，
在许多领域，例如图像识别，自然语言处理，
和推荐系统都发挥了重要作用。
随着DL对硬件计算能力的需求不断增加，越来越多的 DL任务被部署到具有大量计算资源的云中。
由于CPU在在线小批量数据推理任务中可以提供比GPU更低的延迟，
并且在云中有大量CPU在非高峰时段处于空闲状态等待被利用，CPU也广泛用于云环境下的训练与推理中。
例如，FBLearner是Facebook的一个混合DL系统，主要使用GPU进行训练和离线推理，以获得高性能吞吐量，
同时采用CPU进行在线推理和离线训练，以获得低延迟以及避免CPU浪费。NUMA架构是多核CPU发展的趋势，
NUMA可以通过集成多插槽CPU（多个物理CPU堆叠）来提升整体处理器性能，有助于加快DL任务的处理速度。

然而，随着DNN模型往更大更复杂的方向发展，DL对处理器性能，特别是内存带宽上的需求比以往更大，
因为复杂DNN模型的执行过程中伴随有大量的数据访问。
GPU不仅在浮点运算性能方面要强于CPU，更重要的是其内存带宽远大于CPU，
因此当前DL平台的设计主要依赖于GPU的高带宽并主要面向GPU开发，
其CPU的开发并不先进，很多设计都是直接沿用GPU的。
其中一个设计是：DL平台将每个GPU作为一个worker，并为其分配一个实例（指worker上执行批量的进程）
，虽然这样做对高内存带宽的GPU而言已经足够充分利用批处理内部的并行性，
但忽略CPU与GPU的带宽差异，对于低内存带宽的CPU也沿用此设计（此方法称为“单CPU单实例”），
会导致核心之间竞争十分有限的内存带宽，从而产生核心饥饿，并行度严重下降。
Intel发布的基于x86 CPU的高性能DL数学库MKL-DNN通过包括内循环展开在内的
优化方式一定程度上缓解了计算密集型网络层（如：卷积、GEMM）在访存方面的压力，提高了并行速度，
但这些优化对于单元素操作的访存密集型网络层（如：激活、BN）无能为力，导致CPU被迫花费大量时间去执行这些理论复杂度很低的层。
而且，因为DNN模型是逐层计算的，单CPU单实例模式隐式地将DNN层级同步屏障施加在并行执行的多核上，即使计算密集型层
有富余的带宽也没有办法与访存密集型层进行均摊来提高整体带宽利用率。
此外，当前DL平台未重视CPU内部跨NUMA节点的通信问题（如果为每个NUMA分配一个实例
，这些实例间需要同步）。目前的跨NUMA通信框架常采用传统的参数服务器或Ring-All-Reduce(RAR)，但这些框架主要面向分布式，
在单机内部会产生额外开销，如：远程访存、核心占用，导致从单插槽扩展到多插槽的NUMA CPU也没有很多性能提升。

为了解决DL平台中多核CPU由于带宽紧张导致的低效问题以及跨NUMA的低效通信问题，
本章首先通过分析和测试证实了多核CPU在当前DL平台效率低下的主要原因是因为带宽利用率不足，
之后提出了一种新的CPU DL处理系统——ParaX来解决该问题，ParaX的设计主要包括：
（i)单核心单实例方法，将输入数据分割成多个迷你子批次，并为每个CPU核心分配一个实例（称为“单核心单实例”），
而不是之前的“单CPU单实例”，以允许每个核心单独对其批次进行处理， 从而去除执行DNN模型每层的同步屏障，
并通过超轻度调度策略，利用各层执行时间的随机性，充分重叠访存密集型网络层与计算密集型网络层，
以达到均摊访存带宽的目的，提高整体带宽利用率；
(ii)梯度服务器通信机制，由于单核心单实例方法相比单CPU单实例有更多的实例需要同步，
从而进一步增加了NUMA内部的同步时间，
ParaX针对NUMA架构设计了基于共享内存
的核心间高效通信方法，梯度服务器
利用共享内存大大减少了的同步开销，提高了整体训练速度。
实验结果表明，ParaX显著提高了训练和推理的执行速度，在双NUMA Intel 8280 CPU上
对所有测试模型（包括图像识别、自然语言处理、推荐系统）分别提高了1.73倍至 2.93倍的训练速度和2.08倍至2.11倍的推理速度。

\section{研究背景}
\subsection{先进多核CPU}
CPU和GPU在深度学习硬件市场之间的竞争由来已久。
虽然近年来GPU更适合于高吞吐量的应用（模型训练与离线推理），
而 CPU在低延迟任务（在线推理）中表现更好已被广泛接受，但如今CPU也被广泛用于吞吐量要求较高的场景，
比如商业云供应商可以将CPU适应于云中负载的日间周期。
为此，英特尔不仅适用于深度学习应用的新款多核CPU，也发布了CPU端的数学内核DNN库。
本节将简要介绍最先进CPU在硬件参数（核心数量/FLOPs和内存带宽/容量）、专用深度学习加速库、性能（延迟和吞吐量）上的特点。

GPU的内核比CPU多得多。 例如，Tesla P100 GPU有60个SM（流式多处理器），每个SM上都有64个CUDA内核，因此P100上总共有3840个CUDA核心。
相比之下，英特尔CPU最多只有几十个核心。但实际上，他们在峰值性能方面的差异并没有预期那么大。
例如，英伟达Tesla P100单精度峰值FLOPs为10.6万亿次，
而对于两个NUMA Intel Xeon Platinum 8280 CPU（共有56个内核，频率为2.70 GHz，并支持每次64个单精度向量 运算的AVX512指令）的单精度峰值FLOPs性能为$2.70×56×64≈9.66$TFLOPS。
内存带宽与容量。 GPU相比 CPU而言有更高的内存带宽。 例如， P100的片上内存带宽峰值为732 GB/秒，
而最先进的DDR4-SDRAM的理论带宽速率为25.6 GB/秒。
内存带宽是限制CPU吞吐量的主要因素之一。另一方面，CPU的内存容量与GPU相比通常要更大。
例如，主流的商品服务器通常具有256GB主内存，而最先进的GPU也只有最多48GB的片上内存。
我们分别在MXNet上运行 ResNet50、MobileNet-v1 和Inception-BN（批量大小=64），
测试了 i7-7700k CPU（4核），双 NUMA Platinum 8280 CPU（56核）以及Tesla P100 GPU（3840 核）的训练吞吐量。
结果如图\ref{fig:cpu_throughput}显示：虽然CPU在吞吐量方面已经取得了显著进展，但多核 CPU和GPU仍存在巨大的性能差距。我们还比较了8280 CPU和P100 GPU的推理性能（如图\ref{fig:cpu_throughput}所示）。
我们在MXNet上采用单CPU单实例方法，并使用一个小的批次大小来模拟低延迟需求的在线推理场景。
图\ref{fig:cpu_throughput}（a）展示了冷延迟启动（模型不在内存中，先将模型载入内存，再进行推理）分别在CPU和GPU上的效果，CPU具有明显更低的延迟，
这主要是因为：GPU需要额外的初始化工作（例如：载入模型进入GPU内存），而CPU拥有更高的单核频率
图\ref{fig:cpu_throughput}（b）展示了热启动的延迟（模型保留在内存中，直接进行推理），此时CPU在批量大小B>4后，其延迟会比GPU更高，
这是因为当批量大小到达一定程度，延迟也会受吞吐量影响。
需要注意的是冷启动延迟对处理器来说非常关键，因为在云环境中其内存内容切换非常频繁。

\begin{figure}
  \centering
  \includegraphics[width=0.7\linewidth]{figs/i7700k}
  \caption{Throughput comparison.}
  \label{fig:cpu_throughput}
\end{figure}


\begin{figure}
    \centering
    \includegraphics[width=0.9\linewidth]{figs/start_latency_4}
    \caption{Inference latency comparison.}
    \label{fig:cpu_latency}
\end{figure}


\subsection{NUMA架构}
多核CPU常采用NUMA（非一致内存访问）架构，
其中每个CPU有m个NUMA节点，每个NUMA节点都会有其本地内存。
虽然CPU上的m个NUMA节点都能访问到所有主存，但是每个NUMA访问本地内存的速度要快于非本地内存（远程访问）的访问速度。
现行的DL平台都采用单CPU单实例的模式来训练和推理模型。DNN模型需要被大数据集迭代训练多次，每次称为一个epoch。
在每个epoch内，数据集被分割为多个迷你批量，并被输入进入训练迭代中。
执行数据并行时，每个迷你批量又可以被分割为多个批量，每个批量都作为一个实例的输入。
图\ref{fig:one_instance_per_numa}展示了一个单NUMA CPU的训练过程，在每个迭代中，单个实例被分配到整个CPU上，所有核心使用批量内并行方法共同执行整个批量。
所有核心先从内存中读取权重，然后逐层计算梯度，最后更新权重。
神经网络由一系列连续的网络层组成（比如卷积、GEMM、激活、BN等），每个层的输出是下一层的输入。
在单实例单CPU模式下，每个核心都执行一个线程，这些线程组成一个实例逐层执行网络。
现行的DL框架通过MKL-DNN库在充分利用批量内并行，使每个GPU或CPU核心分别执行OP内的部分数据。
图\ref{multi-threads}展示了多核CPU的单实例单CPU模式：$T_{i}$表示核心$i$上的线程，在第$k$层，多个线程从线程池内被唤醒，并行执行该层。

\begin{figure}
  \centering
  \includegraphics[width=0.4\linewidth]{figs/ps-numa-new.pdf}
  \caption{MXNet adopts one-instance-per-CPU for model training，
  where MXNet assigns one single instance to one CPU.
  }
  \label{fig:one_instance_per_numa}
\end{figure}


\begin{figure}
\centering
\includegraphics[width=0.75\linewidth]{figs/multi-threads2.pdf}
\caption{One-instance-per-CPU imposes per-layer barriers.}
\label{multi-threads}
\end{figure}


\section{带宽分析}
为了研究多核CPU在DNN模型是低效的原因，我们首先以一个例子来展示CPU执行DNN网络时各级网络层的带宽和CPU利用率。
图\ref{Rough_speedup}展示了使用Intel 8280在ResNet50执行推理的效果，
图上方是带宽结果，可以看出，8280在执行卷积层时带宽仍有接近一半的剩余，而在执行激活和BN时，带宽几乎被占满了。
图下方是CPU利用率结果，结果显示CPU在执行带宽被占满的激活和BN层时利用率很低，而卷积层保持了接近100\%的利用率。

\begin{figure}
\centering
\includegraphics[width=\linewidth]{figs/cpu_utilization_2.pdf}
\caption{Utilizations of memory bandwidth and CPU cycles
in the inference of ResNet50,
running MXNet on Intel 8280 (one NUMA node with 28 cores).}
\label{Rough_speedup}
\end{figure}

造成以上现象的原因是因为DNN模型中不同的网络层具有不同的访存密度，
访存密度复杂度的物理意义指：CPU单位时间内执行计算所需要的数据量。
显然，当单位时间内的CPU的数据量需求越大，其访存量也会越大。
对于卷积层，其时间复杂度$\xi_c$为：

\vspace{-4mm}
%\begin{equation}
\begin{align*}
\label{e1}
\xi_c = O(BC_{i}C_{o}D_{K}^2D_{F}^2)
\tag*{(1)}
\end{align*}
%\end{equation}

这里 $B$是数据批量大小, $C_{i}$ 是输入通道数,
$C_{o}$是输出通道数,
$D_{K}^2$是卷积核大小,
$D_{F}^2$是特征图尺寸。

其空间复杂度$\psi_c$为输入大小、输出大小与卷积大小之和：

\vspace{-4mm}
%\begin{equation}
\begin{align*}
\label{e2}
\psi_c = O(BC_{i}D_{F}^2 + BC_{o}D_{F}^2 + C_{o}D_{K}^2)
\tag*{(2)}
\end{align*}
%\end{equation}

因此，其访存密度复杂度$I_c$为：

\vspace{-4mm}
%\begin{equation}
\begin{align*}
\label{e30}
I_c &= \psi_c / \xi_c = O(\frac{BC_{i}D_{F}^2 + BC_{o}D_{F}^2 + C_{o}D_{K}^2}{BC_{i}C_{o}D_{K}^2D_{F}^2})\\
&= O(\frac{1}{C_{o}D_{K}^2} + \frac{1}{C_{i}D_{K}^2} + \frac{1}{BC_{i}D_{F}^2}).
\tag*{(3)}
\end{align*}
%\end{equation}

而对于激活或BN这类单元素操作，其对批量中每个元素只执行常数次操作，因此其时间复杂度和空间复杂度都为$O(B)$，其
访存密度复杂度为$O(1)$。对比公式\ref{e3}，显然激活与BN的访存密度远大于卷积，这也是激活与BN相比卷积会占用更多带宽的原因。
根据访存密度的大小，我们将网络层分为两类：计算密集型和访存密集型。
计算密集型层执行的是包含大量复杂算术运算的操作，例如卷积和GEMM；访存密集型操作主要执行的是单元素操作，
例如激活和BN，只有较少的运算复杂度，但会执行大量的访存。
当执行访存密集型操作的内存带宽需求高于带宽上限时，会导致带宽紧张使核心饥饿，最终导致执行时间变长。

另一方面，观察图\ref{Rough_speedup}可以发现，激活与BN相比卷积（公式\ref{e1}）而言，虽然只有很小的$O(B)$时间复杂度，但它们仍然占据了相当
一部分执行时间。对此，我们还分别在单NUMA的英特尔 8280 CPU和P100 GPU上测试了ResNet50，RNN(两层LSTM)和NCF三种类型的网络
中的计算密集型层与访存密集型层的比例。
如图\ref{portions}所示，在CPU上，计算密集型层的执行总时间与访存密集型层的执行总时间是相当的，前者只有后者的1.5倍；
相反在GPU上，前者的时间是后者的2.7倍。也就是说，在CPU上，访存密集型操作占到了总执行时间中相当大的比例，
而访存密集型层的理论运算量远达不到这个级别。


\begin{figure}
  \centering
  \includegraphics[width=0.8\linewidth]{figs/CPUvsGPU.pdf}
  \caption{Inference time ratios ($B=64$) of compute-intensive layers
  vs.~access-intensive layers, respectively for CPU (Intel 8280) and GPU (NVIDIA P100).
  $B=128$ and $256$ have similar results.}
  \label{portions}
\end{figure}

我们进一步分析了计算密集型层与访存密集型层的可扩展性。
图\ref{fig:op_scale}展示了单NUMA 8280 CPU在MXNet上执行ResNet50推理的结果，结果显示：三种操作在线程数不超过7时具有接近的加速比，
但在超过7之后，激活与BN的加速比很难再继续增长，并且此时内存带宽几乎被占满了，
另一方面卷积始终保持着较高的增长，并且即使线程数达到了28，仍然只使用了一半左右的带宽。
虽然访存密集型层相比计算密集型层在多核CPU上的训练或推理有更少的算术运算，
但访存密集型层读取数据的频率要比计算密集型层高得多（以激活层为例：激活层虽然只对每个元素执行1次比较运算，
但运算的前提是核心能够读取到每个元素），
这导致了内存带宽变成了访存密集型层的一个潜在的瓶颈，
与之相反，计算密集型层会执行相比访存更多的计算量，这均摊了访存的消耗。因此，多核CPU的执行DNN的吞吐量很大程度是由带宽的利用率决定的。

\begin{figure}
  \centering
  \includegraphics[width=0.9\linewidth]{figs/bandwidth-scaling-new.pdf}
  \caption{Speedups and utilization of different operations,
  as the numbers of threads increase.}
  \label{fig:op_scale}
\end{figure}

\section{ParaX设计}
为了解决多核CPU中由于带宽紧张导致的低效问题以及跨NUMA的通信低效问题，
本文提出了ParaX系统，ParaX主要包括：1.单核心单实例方法，
改变原来的单CPU单实例方法为每个核心一个实例的单核心单实例，以解除DNN层级的同步屏障，
实现不同类型网络层重叠执行来均摊访存带宽，提高整体带宽利用率；2.梯度服务器通信机制，针对NUMA架构设计了基于共享内存
的核心间高效通信方法，梯度服务器利用共享内存大大减少了的同步开销，提高NUMA CPU内部的核心间通信速度和整体训练速度。
此外，ParaX还提供了适用于分布式训练的x核心单实例方法。

\subsection{单核心单实例方法}
为了缓解内存带宽紧张的问题，我们的基础思路是在不同的核心上重叠不同类型的网络层（包括卷积、GEMM、激活、BN等），
这样可以将计算密集型层（如：卷积和GEMM）富余的带宽 “借”给访存密集层（如：激活和BN）。但如图\ref{fig:overlapping}所示，
单CPU单实例隐式地将DNN网络的层级同步屏障施加在联合处理数据的多个核心上，因此阻碍了“带宽借用”。
ParaX基于数据并行的思想，将迭代的输入数据分成多个批量，
并给每个CPU核心分配一个实例，我们称之为单核心单实例。
如图 \ref{fig:overlapping}b所示，
这允许每个核心在没有同步屏障的情况下单独处理其实例，
使访问密集层能够同时利用计算密集型层的剩余带宽。
请注意，单核心单实例不同于分布式 DL方案，
其中多个 GPU/CPU节点处理相同的模型，每个节点负责训练或推理一个批量：
分布式 DL平台主要是为了集成更多的硬件资源，但无法适应于NUMA架构，
而 ParaX主要侧重于提高现有资源的利用率（内存带宽和 CPU周期）。
通过消除每层的同步屏障，单核心单实例方法为 CPU的多个核心之间的带宽借用提供了机会，
每个核心运行一个线程处理一个实例。为了提高带宽利用率，直观上ParaX需要精心安排核心上的线程，
以便其访问密集型层可以在不同的时间切片中执行。但是，由于每个网络层的执行操作通常是毫秒时间的，
因此对大量线程进行细粒数调度是不切实际的。幸运的是，我们观察到同一批量的相同网络层的执行时间存在一定的随机性，尤其是在线程数量相对较大且网络足够深的情况下。因此，ParaX采用超轻度重叠调度策略，该策略依赖于网络层推进执行时间的随机性，以重叠不同核心上的访存和计算密集型层的执行。图\ref{fig:overlapping}b展示了的一个线程运行DNN模型中第i层的情况，
由于在前i-1层的执行过程中，不同线程的进度具有随机性差异，该线程的执行的访存密集型层与其他线程执行的计算密集型层重叠了起来。ParaX让线程间的访问密集型和计算密集型层在统计上相互重叠，从而在执行不同实例的线程之间隐式地实现了内存带宽借用，均摊了两种不同网络层的访存带宽，提高了带宽利用率。

\noindent
\textbf{延迟启动策略}：基于随机性的重叠调度策略有效地重叠了 DNN模型的访存和计算密集型操作的执行。
但是，重叠调度的一个潜在问题是，在训练迭代的前几层，随机性可能不足以进行足够的重叠。
虽然对于层数大的深层网络来说不是问题，但它可能会影响相对浅层网络的性能，而该网络的层总数很小。
因此，ParaX 使用延迟启动来对重叠调度进行完善，以解决此问题。
考虑一个有$m$个NUMA节点的CPU，每个节点都有$p$个核心。
对于单核心每实例模式，ParaX将$m\times p$个worker线程组织成$m$线程池，
每个线程在一个内核上处理一个实例。我们将每个 NUMA上的$p$个线程划分为每个具有$p/n$线程的$n$个子集，
并从每个NUMA上分别挑选一个子集组成一个启动组（每个启动组由$mp/n$线程组成，共有$n$个启动组）。
在迭代开始时，ParaX接连启动这些启动组，连续两次启动之间的时间间隔为$t$，其中 $t$是可配置参数（通常在几十毫秒的尺度上），以确保启动组在前几层的重叠。


\begin{figure}
  \centering
  \includegraphics[width=0.7\linewidth]{figs/overlap-new.pdf}
  \caption{Layer overlapping.}
  \label{fig:overlapping}
\end{figure}

\subsection{梯度服务器核心通信机制}
ParaX遵循同步SGD（用于保证收敛），在每次训练迭代中，
用梯度对参数进行更新。单核心单实例的一个问题在于：参数更新时，
它可能会随着实例数的增加而间接增加同步开销，这甚至可能会完全抵消层重叠的好处（实验中将对此进行评估）。
当前 DL平台现有的同步机制（如 PS（参数服务器）和 RAR（Ring-all-reduce）是针对分布式方案所设计的，因此不适合 CPU 上多实例的同步。我们简要讨论了 PS 同步效率低下的问题。图\ref{fig:per_core_PS} 展示了在m-NUMA CPU ($m = 2$)采用单核心单实例的基于PS的训练迭代过程。
每个NUMA节点上，都有一个核心都占用一个专用线程作为参数服务器 （PS0/PS1），
其余$p-1$个核心（worker）用于处理$p-1$个实例。Worker（i）通过复制内存中的$W_{i}$来从$m$个服务器$Ps_{i}$中拉取权重$\bm{W}=W_{i} , i = 0,1,…,m-1$,（ii）在实例上计算其梯度$g$，（iii）把梯度$g_{i}$累加到服务器$Ps_{i}$的总梯度$G_{i}$上。PS最终使用$G_{i}$更新它的权重$W_{i}$。
这个机制中，PS不仅需要额外线程进行维护，浪费了CPU核心周期，而且会导致高同步延迟。
与PS不同，RAR通过将实例组织成一个环，在两个不同的阶段与邻居之间交换梯度，从而减少通信。
但是，与PS的问题类似，RAR也需要额外线程进行维护，其环更新也会大大减慢了同步过程（我们将在实验中进行评估）。
为了提高同步效率，ParaX 设计了一个 跨NUMA梯度服务器或Gradient Server（GS）通信机制，
允许所有实例通过共享内存直接访问权重，而不需要额外线程维护。
如图 \ref{fig:per_core_GS}所示，一个 CPU 共运行一个梯度服务器，
该服务器不拥有自己的权重，但与实例共享权重，以消除每次迭代中的高额内存拷贝开销。
在每个 NUMA上都有本地内存中权重的完整副本，以避免在迭代中频繁的远程访存。
算法\ref{alg:gs}展示单核心单实例方法基于梯度服务器机制的每个训练迭代的伪代码，
其中$m$是NUMA节点的数量，$p$是 NUMA上的实例数（等于核心数）
$W_{i}$是共享权重在第$i$个NUMA中的完整副本，存储在其本地内存中($i=0,1,…,𝑚-1$)，
$G$是分布在每个NUMA的本地内存中的全局梯度，$g_{i}^{j}$是第$i$个NUMA上的第$j$个实例
计算的梯度 ($j =0,1,…,p – 1$)。
每个实例分别执行Forward()和Backward()计算本地梯度$g_{i}^{j}$，并累加到聚合全局梯度$G$。
请注意，梯度聚合步骤（$G += g_{i}^{j}$）可能会产生另一个 NUMA的远程访问，
但该步骤只会在每次迭代的最后出现一次，不会影响整体性能。
在所有梯度聚合到$G$后，梯度服务器将在$m$个NUMA的本地内存中将$G$更新到权重$W_{i}$上。$W_{i}$的m个副本是相同的，因为它们的更新方式完全相同。

\begin{figure}
  \centering
  \includegraphics[width=0.9\linewidth]{figs/inst-per-core-ps.pdf}
  \caption{One-instance-per-core + PS.
  For brevity
  we omit the lines from instances to $G_i$ for computing gradients
  (blue arrowed lines in Figure~\ref{fig:one_instance_per_numa}).
  }
  \label{fig:per_core_PS}
\end{figure}


\begin{figure}
  \centering
  \includegraphics[width=0.9\linewidth]{figs/inst-per-core-gs.pdf}
  \caption{One-instance-per-core + gradient server (GS).
  All instances as well as the GS share the same weights
  and thus GS does not need to ``copy weights'' compared to PS.
  }
  \label{fig:per_core_GS}
\end{figure}

\begin{algorithm}[t]
\caption{单核心单实例基于梯度服务器的训练迭代算法}
\label{alg:gs}
\begin{algorithmic}
\FOR{$i=0,1,\cdots,m$-$1$ in parallel}
\FOR{$j=0,1,\cdots,p$-$1$ in parallel}
\STATE /**** 初始化 ****/
\STATE 实例inst$_{i}^{j}$.绑定(核心core$_{i}^{j}$)
\IF{网络时浅层的}
\STATE 根据延迟分组计算延迟$t$
\STATE inst$_{i}^{j}$.sleep($t$)
\ENDIF
\STATE /**** 前向/反向过程 ****/
\STATE Forward($d_{i}^{j}$, $W_{i}$)
\STATE $g_{i}^{j}$=Backward($d_{i}^{j}$, $W_{i}$)
\STATE $G$ += $g_{i}^{j}$ // 互斥操作
\ENDFOR
\ENDFOR
\STATE /**** 同步 ****/
\FOR{$i=0,1,\cdots,m$-$1$ in parallel}
\STATE $W_{i}$ += $G \cdot lr$ // 由梯度服务器执行
\ENDFOR
%\vspace{-3mm}
\end{algorithmic}
\end{algorithm}

\subsection{x核心单实例方法}
与现有的单CPU单实例模式相比，每个核心一个实例可增加实例数量，并有效提高多核 CPU 的内存带宽利用率，
但是对于分布式训练，迭代中的使用太多实例可能会影响训练精度。考虑一个$n$台服务器的集群，
每台服务器都有$m$个NUMA节点，每个NUMA节点有$p$个核心，单核心单实例模式一共有$n\times m\times p$个实例。
当实例的总批量大小过大或每个实例样本数太小时会降低训练精度。
为了避免分布式环境下的精度下降问题，我们设计了更通用的多核心每实例模式，这个模式下每个实例由 $x$内核共同处理，而不是单个。
现有的研究表明，对于中等大小集群的分布式训练，当使用适合的批量大小（例如 64）对每个 NUMA 节点最多4个实例采进行训练，其精度几乎保持不变。

\section{实验}
我们在 MXNet上实现了 ParaX。
我们实现了单核心每实例以及多核心每实例模式，重叠调度和梯度服务器。
测试机安装了一部双 socket(NUMA) Intel Platinum 8280 CPU，
每个NUMA拥有28个 2.70GHz核心（共56个核心）、39424KB L3-缓存、192GB六通道 DDR4-2933内存
和两个Intel 750 PCIE 400GB NVM-Express SSD。
我们通过对各类DNN模型进行训练和推理来评估 ParaX，涉及的DNN模型包括图像识别
（ResNet、MobileNet和Inception-BN）和自然语言处理（LSTM和 GNMT）。
我们首先简要介绍这些 DNN 模型。
ResNet通过在 DNN网络中增加快捷方式连接，以避免梯度在层数高时消失和爆炸的问题；
MobileNet用深度纵向卷积取代常规卷积以减少参数，同时保持准确性；
Inception-BN在Inception网络中的卷积层之后添加BN层，Inception中的每个子模块中包含各种大小类型的卷积内核，
以适应不同比例的功能；LSTM（long short-term memory）使用反馈连接选择性地记忆长序列中的有用模式，
一个LSTM结构具有四个GEMM层、四个sigmoid激活层和两个tanh激活层。
如果不具体说明，我们测试中使用的数据类型为 FP32。
ParaX中每个批量的大小设置相对简单：由于 ParaX对多核 CPU采用单核心单实例，
因此当批量大小过大时（>64）往往会超过内存容量，而过小的批量大小（<32）将导致训练精度不准确以及吞吐量较低。
因此，如果不做具体说明，用于训练的每个实例的批量大小为$B=64$来确保收敛，
因此总批量大小是实例数的$B$倍，推理总批量大小为固定值（$64×56 = 3584$）。
一个例外是：对于图像网上的ResNet50/ResNet101，当使用 56实例进行训练时，ParaX 使用 $B = 32$（而不是 64），
因此总批量大小为 1792。对于推理，ParaX 使用固定的总批量大小为 1792（而不是 3584）时，以避免超过内存容量。

在本节的其余部分，我们的实验将验证以下问题：
\begin{itemize}
\item 与单CPU单实例的原始 MXNet 相比，ParaX 在训练和推断各种 DNN 模型时表现如何？资源利用度和可扩展性如何？
\item 梯度服务器机制在模型训练的参数更新中的表现与当前最先进的 PS 和 RAR 通信机制相比如何？
\item 批量大小对 ParaX 的训练和推理性能以及内存足迹有何影响？
\item ParaX 在各种应用（包括图像识别和 NLP）中的表现如何？
\end{itemize}

\subsection{微基准测试}
我们评估 ParaX 在训练和推理 ResNet（使用50层并使用 ImageNet）、
MobileNet-v1（使用28层并使用ImageNet）和 LSTM（每层有2层650个隐藏神经元并使用福尔摩斯数据集）中的吞吐量。
ParaX 采用单核心单实例和梯度服务器机制，并依靠随机性进行层重叠。
因为这些测试网络足够深，所以ParaX不采用延迟启动。
为了进行比较，我们还评估了MXNet的原始方法，它采用单CPU单实例，因此没有训练的同步开销。
图\ref{speedup-ratio}显示了Parax到MXNet的提速，其中训练和推理的提速分别为1.73×至2.93×和2.08×至2.11×。
与 ResNet和MobileNet-v1不同，LSTM的训练对实例批量大小（$B$）很敏感，
因此我们还评估了$B = 128$ 和 256 的LSTM MXNet 的吞吐量，结果分别有2.76×和2.49×的提速。
ParaX的性能优势主要来自两个因素，即（i）梯度服务器更新机制（我们将在下个子节中评估），
（ii）计算和访问密集型层的重叠。因此，我们还测量了重叠程度，图\ref{util}展示了在 ParaX上ResNet50的推理过程中的卷积/BN/激活操作的比例，
结果表明不同种类的操作的得到了有效的重叠。
我们进一步测量了ParaX 上ResNet50推理过程中的 CPU利用率（总批量大小为 1792）。
图\ref{train-scale}显示 CPU利用率始终保持高位（用vtune测试），整个训练程序的平均利用率高达94.8\%。
我们还测试了推理期间的平均内存带宽利用率，大约为88.6\%，远高于原来的MXNet。
与当前 DL平台采用的单CPU单实例相比，ParaX显著提高了 CPU和内存带宽利用率。
我们还评估了 ParaX 的可扩展性，分别与MXNet上的ResNet50和 MobileNet-v1训练、推理进行比较。
我们将涉及的核心数量从1个增加到56个，并测量ParaX和MXNet相应的吞吐量。
结果如图\ref{train-scale}和\ref{infer-scale}中所示，其中Parax在训练和推理方面都比MXNet具有更好的可扩展性，
这是因为 ParaX不仅在训练和推理中 最大限度地利用了内存带宽和 CPU周期，而且还最大限度地减少了多实例训练中参数更新的同步开销。

\begin{figure}
  \centering
  \includegraphics[width=0.7\linewidth]{figs/speedup-ratio}
  \caption{parax vs. MXNet.}
  \label{speedup-ratio}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=\linewidth]{figs/OP_ratio_utilization}
  \caption{OP ratios \& CPU utilization.}
  \label{util}
\end{figure}


\begin{figure}
  \centering
  \includegraphics[width=0.95\linewidth]{figs/ps-gs-scaling}
  \caption{Training scalability.}
  \label{train-scale}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=0.95\linewidth]{figs/ps-gs-scaling-inference}
  \caption{Inference scalability.}
  \label{infer-scale}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=0.95\linewidth]{figs/training_throughput.pdf}
  \caption{CPUs with parax achieve comparable training TP with GPUs}
  \label{GPU-compare}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=0.95\linewidth]{figs/inference_throughput.pdf}
  \caption{CPUs with parax achieve similar or even higher inference TP.}
  \label{infer-throughput}
\end{figure}


\subsection{梯度服务器}
我们将 ParaX 的 GS（梯度服务器）与最先进的 PS（参数服务器）和 RAR（ring-all-reduce）机制进行比较。我们首先测试在ParaX上对 ResNet50 使用不同实例总数训练时的吞吐量，测试分别采用梯度服务器 （GS）、参数服务器 （PS） 和Ring-all-reduce（RAR）机制。PS 机制已集成到原始 MXNet 中，RAR 从horovod中移植。由于 PS 和 RAR 最初是用于分布式方案的，它们都需要一个专用线程来维护，并在每个NUMA 节点上占用一个核心。因此，2-NUMA CPU 最多仅保留$ 56 + 2 = 54$个核心，以用于训练。由于每个实例必须占用相同数量的核心来保证每个实例的效率一致，因此某些测试中会浪费掉一些核心。例如，如果我们启动4个实例，则每个实例最多有13个核心，因此可用于训练的核心总数为52个，浪费掉了$54-52=2$个核心。
为了公平起见，我们在比较不同机制时仍使用相同数量的核心（即使所有 56个核心都可用于GS框架的训练）。结果见图\ref{communication_throughput}，括号外的数字表示总实例个数，括号中的数字表示可用的核心总数，例如，4（52）的结果表示使用4个实例和52个核心时的训练。
随着实例数的增加，GS 机制的吞吐量几乎总是保持增长，相比之下，当使用超过4个实例后，PS 和 RAR 机制的吞吐量就开始变低。对于使用 54 个实例的PS和 RAR，我们比较 1（56）和 54（54）的两个结果，可以看出迭代的同步开销几乎抵消了层重叠的所有加速，这是因为随着实例数的增加，同步的开销也变得越来越大。需要注意的是，当对所有56个内核仅使用一个实例时，三个机制的吞吐量相同，因为此时不需要核心同步。
为了了解 GS（梯度服务器）相对于 PS（参数服务器）和 RAR（环形所有还原器）的吞吐量优势，我们还在训练ResNet50模型时测量了每次迭代中参数更新的延迟。结果见\ref{update_latency}，两次测量采用了相同的配置，三种机制中的延迟都随着实例数的增加而增加。与 PS 和 RAR 相比，GS 机制的延迟始终要小得多，因为 GS 机制使用共享内存为每个 NUMA 保留一个共享权重副本，从而消除了高额的内存拷贝开销。除了较低的更新开销外，本实验中未显示的另一个重要优势是，GS 可以使用所有内核进行训练，而 PS 和 RAR 必须浪费至少两个核心专用于参数更新。
我们还测量了三种机制下训练过程中参数同步的数据访问总量（包括数据读取量和数据写入量）。结果见图\ref{data_access}，其中PS机制的数据访问量大约是GS的两倍。这是因为在 PS 中，拉取参数和推送梯度操作都会执行内存拷贝，而在 GS 中，由于共享权重机制，仅推送梯度需要执行内存拷贝。请注意，尽管 RAR 的数据访问量仅略高于 GS，但它的同步开销要高得多，因为实例被组织成环状，并分两个阶段与顺时针的邻居交换梯度。

\begin{figure}
  \centering
  \includegraphics[width=0.95\linewidth]{figs/communication_throughput.pdf}
  \caption{Gradient server vs. PS/RAR
  (adopting one-instance-per-$x$-core).
  For example, 4(52) means 4 instances on 52 cores, i.e., one-instance-per-13-core.}
  \label{communication_throughput}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=0.95\linewidth]{figs/communication_latency.pdf}
  \caption{Update latency comparison (adopting one-instance-per-$x$-core).}
  \label{update_latency}
\end{figure}

\begin{figure}[htb]
  \centering
  \includegraphics[width=0.95\linewidth]{figs/communication-data.pdf}
  \caption{Data accessed for parameter synchronization
  in an iteration (adopting one-instance-per-$x$-core).}
  \label{data_access}
\end{figure}


\subsection{批量大小的影响}
为了了解批量大小对性能的影响，在此小节中，我们测试了不同批量大小下ResNet50和MobileNet-v1 在 ParaX 上训练和推理的吞吐量。对于训练，我们在每个实例上使用固定批量大小$B = 64$，并将实例（n）的数量从1增加至56。对于推理，我们首先使用1、4、14和56个实例，每个实例的$B=1$，然后修改$B= 56$，并将实例数增加到64 个。结果见图\ref{train_scale}和\ref{infer_scale}，在总批量规模相对较高后，吞吐量在一开始增长较快后续增长较缓慢。对于训练，这是因为B达到64×14 之后，实例数对内存带宽利用率仅有边际影响;而对于推理，在B达到16×56之后，批量大小对计算效率只有边际影响。
图\ref{train_scale}和\ref{infer_scale}还标记了在 ParaX 上训练和推断 ResNet50 和 MobileNet-v1 时的内存足迹（以 GB 为单位）。训练的内存略多于推论，因为它含有的反向传播阶段比前向传播更加复杂。如图所示，ResNet 不能使用总批量大小$3584(=64 × 56)$，因为此时它将超过最大内存容量（192 GB）。由于操作已经足够重叠，批量大小和内存容量不是 ParaX 性能的瓶颈。我们还测试了 P100 上的各种总批量大小，由于 GPU 内存容量相对较小（P100 为 16 GB），ResNet50 和 MobileNet-v1 在其上的最大批量总和为 448。


\begin{figure}
  \centering
  \includegraphics[width=\linewidth]{figs/train-batchsize-mem}
  \caption{Training throughput vs.~total batch size
  $=B\times n$ ($n$ is instance number) on parax.
  Memory footprint (in GB) is labeled on the bars.
  }
  \label{train_scale}
\end{figure}

\begin{figure}
  \centering
  \includegraphics[width=\linewidth]{figs/infer-batchsize-mem}
  \caption{Inference throughput vs.~total batch size $=B\times n$ ($n$ is instance number)
  on parax.
  Memory footprint (in GB) is labeled on the bars.
  }
  \label{infer_scale}
\end{figure}


\subsection{应用}
在本节中，我们使用更多的应用程序（包括图像识别和自然语言处理）来评估 ParaX。

\textbf{图像识别}。我们在ResNet、MobileNet、Inception-BN上测试ParaX的训练和推理的吞吐量。
在英特尔 8280 CPU 上，有两个 NUMA 节点，每个节点都有 28 个内核，我们通过增加实例数（从 1 到 56），
以比较单CPU单实例、多核心单实例和单核心单实例上的吞吐量。数据集包括ImageNet （ILSVRC2012）和 CIFAR10。
ImageNet 拥有共 1000 个类别的 1280000 张训练图像。ImageNet中的图像都是具有高分辨率的自然图像（224  ×  224），
而CIFAR10 数据集则包含 10个类别的60000个相对较小的图像（32 × 32）。
图\ref{Imagenet_training}和\ref{Imagenet_inference}分别显示了5个CNN模型（ResNet50、ResNet101、MobileNet-v1、MobileNet-v2、Inception-BN）
在ImageNet上训练和推理的吞吐量。正如在本节开头介绍的，在训练中，每个实例批量的大小为$B = 64$（56 个实例的ResNet是$B = 32$除外），
推理的总批量大小为3584 的固定值（ResNet除外，为 1792）。在这五种类型的网络中，56个实例都实现了最佳性能，这表明单核心单实例模式有效地提高了内存带宽和CPU的利用率。
图\ref{Imagenet_training}和\ref{Imagenet_inference}分别展示了 ResNet50 和 ResNet101在CIFAR10 的吞吐量，对于训练，每例批量大小 $B = 64$，对于推理，总批量大小固定为$64×56 =3584$。
结果与 ImageNet的类似，但在ResNet50 和 ResNet101的推理中，28个实例实现了最佳吞吐量。
这主要是因为用于Cifar10的ResNet的通道数量被压缩了，这增加了内存带宽的依赖，从而降低了重叠调度的有效性。

\textbf{自然语言处}理。我们评估Parax在训练和推理Word-LM（单词语言模型）和GNMT（谷歌神经机器翻译）模型时的吞吐量，与图像识别类似，
实例的数量也从1增加到56，Word-lm使用福尔摩斯数据集为，Gnmt使用Iwslt2015数据集。对于Word-LM，我们训练和推理一个双层LSTM，
每层有 650 个隐藏的神经元。图 \ref{Word-LM/GNMT.} 显示了Word-LM 用于训练（每实例批量大小$B = 64$）和推理（总批量大小$=64×56=3584$）时的吞吐量。
结果与 CNN 测试结果类似，该模型的吞吐量随着实例数的增加而增加，但Word-LM 的可扩展性优于 CNN 模型，因为它主要包含 GEMM（而不是卷积）操作。
我们测试的GNMT模型每层有 128 个隐藏神经元其他实验配置与Word-LM相同。GNMT 由三个独立的网络（编码器、解码器和注意）组成，
其中编码器和解码器各包含一个八层 LSTM，以便高精度地翻译整个句子。结果见图\ref{Imagenet_training}，其中吞吐量按每秒翻译的 （1000） 字数进行测量。
从结果中，我们可以得出了与Word-LM实验类似的结论。

  \begin{figure}
    \centering
    \includegraphics[width=\linewidth]{figs/imagenet_training.pdf}
    \caption{Training with ImageNet.}
    \label{Imagenet_training}
  \end{figure}

  \begin{figure}
    \centering
    \includegraphics[width=\linewidth]{figs/imagenet_inference.pdf}
    \caption{Inference with ImageNet.}
    \label{Imagenet_inference}
  \end{figure}

  \begin{figure}
    \centering
    \includegraphics[width=\linewidth]{figs/word-lm.pdf}
    \caption{Inference with ImageNet.}
    \label{Word-LM/GNMT.}
  \end{figure}

  \begin{figure}
    \centering
    \includegraphics[width=\linewidth]{figs/ncf.pdf}
    \caption{Inference with ImageNet.}
    \label{NCF.}
  \end{figure}

 \begin{table}[!t]
   \caption{Latency for training ResNet50
   (millisec per call).}
   \label{resnet50-calls}
   \centering
   \begin{tabular}{|c|c|c|c|c|c|}
   \hline
   Inst.&1&4&14&28&56\\
   \hline
   Conv&12.5&11.85&11.82&11.82&11.82\\
   \hline
   BN&8.6&2.73&2.3&2.28&2.28\\
   \hline
   Act&5.08&1.5&1.35&1.34&1.34\\
   \hline
   \end{tabular}
 \end{table}


\begin{table}[!t]
  \caption{OP latency in training Word-LM
  (millisec per call).}
  %{Aggregate OP speed for LSTM training (ms/call)}
  \label{lstm-calls}
  \centering
  \begin{tabular}{|c|c|c|c|c|c|}
  \hline
  Inst.&1&4&14&28&56\\
  \hline
  GEMM&1.28&0.64&0.49&0.46&0.44\\
  %\hline
  Act&0.147&0.047&0.031&0.028&0.026\\
  %\hline
  Embed&31.53&4.38&0.98&0.746&0.633\\
  \hline
  \end{tabular}
\end{table}


  \begin{table}[!t]
    \caption{OP latency for training NCF
    (millisec per K calls).}
    %{Aggregate OP speed for NCF training (ms/kcalls)}
    \label{ncf-calls}
    \centering
    \begin{tabular}{|c|c|c|c|c|c|}
    \hline
    Inst.&1&4&14&28&56\\
    \hline
    GEMM&232.5&107.7&88.9&88.4&88\\
    %\hline
    Act&192.3&46.9&16.5&13.9&12.4\\
    %\hline
    Embed&34&12.5&8&7.5&7.4\\
    \hline
    \end{tabular}
\end{table}
	
\section{本章小结}
现今CPU被大量用于云端的DL的训练和推理任务，这避免了CPU资源在非高峰期处于闲置状态。然而，最先进的DL平台无法高效支持多核CPU的训练和推理，因为这些平台将单个实例分配给整个CPU（与基于 GPU的DL一样），这会导致网络层的同步执行，从而导致间歇性内存带宽争用和CPU饥饿。
本章提出了Parax，可有效的提高内存带宽的利用率，将DL扩展到多核CPU。ParaX的关键思想是为每个核心分配一个实例（而不是整个 CPU一个实例），以便核心可以重叠执行不同操作。ParaX 设计了超轻调度策略和梯度服务器机制。我们在MXNet的基础上实现了Parax，并评估了各种模型，结果表明，ParaX
与现有DL平台的多核CPU方法相比性能要高得多。
未来，我们计划完善ParaX，以充分支持在多核 CPU集群上进行分布式深度学习，此外，ParaX 遵循同步 SGD 以确保收敛，我们将进一步研究基于异步 SGD 的 ParaX。我们预计 ParaX 在基于 MKL-DNN 的其他平台（如 TensorFlow 和 PyTorch）实现类似的多核 CPU 加速效果，因为它们都存在每层同步屏障导致的低内存带宽利用率的问题。目前，ParaX仅在MXNet上实现，我们将继续在其他流行的深度学习平台上实现ParaX。我们还将ParaX与张量编译器集成。此外，带宽竞争问题可以通过增加一个额外的片上高带宽缓存来解决，该缓存也将在未来进行研究。ParaX 的源代码开源在。
